/*******************************************************************************
*
* Copyright (c) 2015 Bosch Software Innovations GmbH Germany.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
*    http://www.eclipse.org/legal/epl-v10.html
* The Eclipse Distribution License is available at
*    http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*    Bosch Software Innovations GmbH - Please refer to git log
*
*******************************************************************************/

/*
 * This connectivity statistics object is optional and single instance only
 *
 *  Resources:
 *
 *          Name         | ID | Oper. | Inst. | Mand.|  Type   | Range | Units | Descripton |
 *  SMS Tx Counter       |  0 |   R   | Single|  No  | Integer |       |       |            |
 *  SMS Rx Counter       |  1 |   R   | Single|  No  | Integer |       |       |            |
 *  Tx Data              |  2 |   R   | Single|  No  | Integer |       | kByte |            |
 *  Rx Data              |  3 |   R   | Single|  No  | Integer |       | kByte |            |
 *  Max Message Size     |  4 |   R   | Single|  No  | Integer |       | Byte  |            |
 *  Average Message Size |  5 |   R   | Single|  No  | Integer |       | Byte  |            |
 *  StartOrReset         |  6 |   E   | Single|  Yes | Integer |       |       |            |
 */

#include "liblwm2m.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

// Resource Id's:
#define RES_O_SMS_TX_COUNTER            0
#define RES_O_SMS_RX_COUNTER            1
#define RES_O_TX_DATA                   2
#define RES_O_RX_DATA                   3
#define RES_O_MAX_MESSAGE_SIZE          4
#define RES_O_AVERAGE_MESSAGE_SIZE      5
#define RES_M_START_OR_RESET            6

typedef struct {
	int64_t smsTxCounter;
	int64_t smsRxCounter;
	int64_t txDataByte;             // report in kByte!
	int64_t rxDataByte;             // report in kByte!
	int64_t maxMessageSize;
	int64_t avrMessageSize;
	int64_t messageCount;         // private for incremental average calc.
	bool collectDataStarted;
} conn_s_data_t;

static uint8_t prv_set_tlv(lwm2m_tlv_t *tlvP, conn_s_data_t *connStDataP)
{
	switch (tlvP->id) {
	case RES_O_SMS_TX_COUNTER:
		lwm2m_tlv_encode_int(connStDataP->smsTxCounter, tlvP);
		tlvP->type = LWM2M_TYPE_RESOURCE;
		return (0 != tlvP->length) ? COAP_205_CONTENT : COAP_500_INTERNAL_SERVER_ERROR;
		break;
	case RES_O_SMS_RX_COUNTER:
		lwm2m_tlv_encode_int(connStDataP->smsRxCounter, tlvP);
		tlvP->type = LWM2M_TYPE_RESOURCE;
		return (0 != tlvP->length) ? COAP_205_CONTENT : COAP_500_INTERNAL_SERVER_ERROR;
		break;
	case RES_O_TX_DATA:
		lwm2m_tlv_encode_int(connStDataP->txDataByte / 1024, tlvP);
		tlvP->type = LWM2M_TYPE_RESOURCE;
		return (0 != tlvP->length) ? COAP_205_CONTENT : COAP_500_INTERNAL_SERVER_ERROR;
		break;
	case RES_O_RX_DATA:
		lwm2m_tlv_encode_int(connStDataP->rxDataByte / 1024, tlvP);
		tlvP->type = LWM2M_TYPE_RESOURCE;
		return (0 != tlvP->length) ? COAP_205_CONTENT : COAP_500_INTERNAL_SERVER_ERROR;
		break;
	case RES_O_MAX_MESSAGE_SIZE:
		lwm2m_tlv_encode_int(connStDataP->maxMessageSize, tlvP);
		tlvP->type = LWM2M_TYPE_RESOURCE;
		return (0 != tlvP->length) ? COAP_205_CONTENT : COAP_500_INTERNAL_SERVER_ERROR;
		break;
	case RES_O_AVERAGE_MESSAGE_SIZE:
		lwm2m_tlv_encode_int(connStDataP->avrMessageSize, tlvP);
		tlvP->type = LWM2M_TYPE_RESOURCE;
		return (0 != tlvP->length) ? COAP_205_CONTENT : COAP_500_INTERNAL_SERVER_ERROR;
		break;
	default:
		return COAP_404_NOT_FOUND;
	}
}

static uint8_t prv_read(uint16_t instanceId, int *numDataP, lwm2m_tlv_t **dataArrayP, lwm2m_object_t *objectP)
{
	uint8_t result;
	int i;

	// this is a single instance object
	if (instanceId != 0) {
		return COAP_404_NOT_FOUND;
	}

	// is the server asking for the full object ?
	if (*numDataP == 0) {
		uint16_t resList[] = {
			RES_O_SMS_TX_COUNTER,
			RES_O_SMS_RX_COUNTER,
			RES_O_TX_DATA,
			RES_O_RX_DATA,
			RES_O_MAX_MESSAGE_SIZE,
			RES_O_AVERAGE_MESSAGE_SIZE
		};
		int nbRes = sizeof(resList) / sizeof(uint16_t);

		*dataArrayP = lwm2m_tlv_new(nbRes);
		if (*dataArrayP == NULL) {
			return COAP_500_INTERNAL_SERVER_ERROR;
		}
		*numDataP = nbRes;
		for (i = 0; i < nbRes; i++) {
			(*dataArrayP)[i].id = resList[i];
		}
	}

	i = 0;
	do{
		result = prv_set_tlv((*dataArrayP) + i, (conn_s_data_t *) (objectP->userData));
		i++;
	} while (i < *numDataP && result == COAP_205_CONTENT);

	return result;
}

static void prv_resetCounter(lwm2m_object_t *objectP, bool start)
{
	conn_s_data_t *myData = (conn_s_data_t *) objectP->userData;

	myData->smsTxCounter = 0;
	myData->smsRxCounter = 0;
	myData->txDataByte = 0;
	myData->rxDataByte = 0;
	myData->maxMessageSize = 0;
	myData->avrMessageSize = 0;
	myData->messageCount = 0;
	myData->collectDataStarted = start;
}

static uint8_t prv_exec(uint16_t instanceId, uint16_t resourceId,
			uint8_t *buffer, int length, lwm2m_object_t *objectP)
{
	// this is a single instance object
	if (instanceId != 0) {
		return COAP_404_NOT_FOUND;
	}

	if (length != 0) {
		return COAP_400_BAD_REQUEST;
	}

	switch (resourceId) {
	case RES_M_START_OR_RESET:
		prv_resetCounter(objectP, true);
		return COAP_204_CHANGED;
	default:
		return COAP_405_METHOD_NOT_ALLOWED;
	}
}

static void prv_close(lwm2m_object_t *objectP)
{
	lwm2m_free(objectP->userData);
	lwm2m_list_free(objectP->instanceList);
}

void conn_s_updateTxStatistic(lwm2m_object_t *objectP, uint16_t txDataByte, bool smsBased)
{
	conn_s_data_t *myData = (conn_s_data_t *) (objectP->userData);

	if (myData->collectDataStarted) {
		myData->txDataByte += txDataByte;
		myData->messageCount++;
		myData->avrMessageSize = (myData->txDataByte + myData->rxDataByte) /
					 myData->messageCount;
		if (txDataByte > myData->maxMessageSize) {
			myData->maxMessageSize = txDataByte;
		}
		if (smsBased) {
			myData->smsTxCounter++;
		}
	}
}

void conn_s_updateRxStatistic(lwm2m_object_t *objectP, uint16_t rxDataByte, bool smsBased)
{
	conn_s_data_t *myData = (conn_s_data_t *) (objectP->userData);

	if (myData->collectDataStarted) {
		myData->rxDataByte += rxDataByte;
		myData->messageCount++;
		myData->avrMessageSize = (myData->txDataByte + myData->rxDataByte) /
					 myData->messageCount;
		if (rxDataByte > myData->maxMessageSize) {
			myData->maxMessageSize = rxDataByte;
		}
		myData->txDataByte += rxDataByte;
		if (smsBased) {
			myData->smsRxCounter++;
		}
	}
}

lwm2m_object_t *get_object_conn_s(void)
{
	/*
	 * The get_object_conn_s() function create the object itself and return
	 * a pointer to the structure that represent it.
	 */
	lwm2m_object_t *connObj;

	connObj = (lwm2m_object_t *) lwm2m_malloc(sizeof(lwm2m_object_t));

	if (NULL != connObj) {
		memset(connObj, 0, sizeof(lwm2m_object_t));

		/*
		 * It assign his unique ID
		 * The 7 is the standard ID for the optional object "Connectivity Statistics".
		 */
		connObj->objID = LWM2M_CONN_STATS_OBJECT_ID;
		connObj->instanceList = lwm2m_malloc(sizeof(lwm2m_list_t));
		if (NULL != connObj->instanceList) {
			memset(connObj->instanceList, 0, sizeof(lwm2m_list_t));
		} else   {
			lwm2m_free(connObj);
			return NULL;
		}

		/*
		 * And the private function that will access the object.
		 * Those function will be called when a read/execute/close
		 * query is made by the server or core. In fact the library don't need
		 * to know the resources of the object, only the server does.
		 */
		connObj->readFunc = prv_read;
		connObj->executeFunc = prv_exec;
		connObj->closeFunc = prv_close;
		connObj->userData = lwm2m_malloc(sizeof(conn_s_data_t));

		/*
		 * Also some user data can be stored in the object with a private
		 * structure containing the needed variables.
		 */
		if (NULL != connObj->userData) {
			prv_resetCounter(connObj, false);
		} else   {
			lwm2m_free(connObj);
			connObj = NULL;
		}
	}
	return connObj;
}
